/* eslint-disable react/prop-types */
/* eslint-disable no-unused-expressions */
import React, { Component } from 'react';
import propTypes from 'prop-types';

import TouchEvent from '../core';
import util from '../util';
import Pinch from '../Pinch';

/**
 * 插入index，insertValue在currentValue前后
 * @param {*} [arr=[]]
 * @param {*} currentValue
 * @param {*} insertValue
 * @returns
 */
function insertItem(arr = [], currentValue, insertValue) {
    if (insertValue !== undefined) {
        const currentIndex = arr.findIndex(i => i === currentValue);
        if (insertValue > currentValue) {
            arr.splice(currentIndex + 1, 0, insertValue);
        } else {
            arr.splice(currentIndex, 0, insertValue);
        }
    }
    return arr;
}

/*
* 1. 如果不可loop
*    只需要更改transform，可以指定缓存dom数量，避免无限item
* 2. 可loop
*    需要有dom复用，可以指定缓存dom数量
*/

export class TouchProps {
    enable = true

    distance = 100
}

function toggleTransition(dom = document.body, add = true, transitionTime = 300) {
    if (add && dom && dom.style) {
        dom.style.cssText += `
            -webkit-transition: transform ${transitionTime}ms;
            transition: transform ${transitionTime}ms;
        `;
        return;
    }
    dom && dom.style && (dom.style.cssText += `
        -webkit-transition: none;
        transition: none;
    `);
}

/**
 * | 0 1 2 3 4 5 正常数组
 * | -2 -1 0 1 2 新增项数组
 * 为了保证current不变我们需要用偏移量去矫正current保证state中的current不变，然后我们外部获得的也的确是list中的索引
 * @augments {Component<{ height: string, touch: TouchProps, space: string }>}
 */
export default class Swiper extends Component {
    static propTypes = {
        current: propTypes.number,
        list: propTypes.array,
        loop: propTypes.bool,
        direction: propTypes.string,
        pinch: propTypes.bool,
        keyEvent: propTypes.bool,
        onChange: propTypes.func,
        onSwiperMoving: propTypes.func,
        style: propTypes.object,
        autoPlay: propTypes.bool,
        autoPlayTime: propTypes.number,
        touch: propTypes.shape({
            enable: propTypes.bool,
            distance: propTypes.number,
        }),
        pageHideDisableAutoPlay: propTypes.bool,
        space: propTypes.string,
        renderItem: propTypes.func,
        renderChildren: propTypes.func,
        // eslint-disable-next-line react/no-unused-prop-types
        follow: propTypes.bool, // 是否跟随移动
        preventDefault: propTypes.bool,
        cacheDOMLen: propTypes.number, // item缓存个数
        transitionTime: propTypes.number, // 动画执行时间（毫秒）
    }

    static defaultProps = {
        current: 0, // 显示指定元素
        list: [],
        loop: true, // 循环
        direction: 'lr', // 滚动方向 X: lr  Y: ud
        pinch: false, // 是否启用缩放
        keyEvent: false, // 是否启用key event
        style: {},
        autoPlay: false, // 是否自动播放
        autoPlayTime: 5000,
        space: '10px', // 两个元素间间隔
        follow: true, // 是否跟随移动
        preventDefault: false, // 是否阻止默认事件
        cacheDOMLen: 3, // 缓存元素个数
        touch: {
            enable: true, // 是否响应触摸事件
            distance: 100, // 触发滑动的有效距离
        },
        pageHideDisableAutoPlay: true,
        renderItem: () => null,
        renderChildren: () => null,
        onChange: () => {}, // 监听变化
        onSwiperMoving: () => undefined, // 监听移动
        transitionTime: 300,
    }

    constructor(props) {
        super(props);
        this.timeout = new util.Timeout();
        // current item 在 cacheDOM 数组中所处的位置
        // 优化点，0 [1, 2, 3, 4, 5] 6 7
        // 我们只有在current靠近1，5的时候，才需要更新dom
        this.currentItemOffset = Math.ceil(this.props.cacheDOMLen / 2) - 1;
        // this.currentItemOffset = 0
    }

    state = {
        current: this.props.current,
    }

    componentDidMount() {
        this.setDOMStyle();
        !this.props.pinch && this.listenChange();
        this.props.keyEvent && this.addKeyEvents();
        this.startAutoPlay();
        this.addPageListener();
    }

    componentDidUpdate(prevP) {
        this.resetCurrent(prevP);
    }

    componentWillUnmount() {
        this.timeout.clear();
        this.stopAutoPlay();
        this.removePageListener();
    }

    resetCurrent = (prevP) => {
        // 找寻之前数组第一个的索引
        const firstIndex = this.props.list.findIndex(i => (i === prevP.list[0]));
        if (firstIndex > 0) {
            // 如果找到了之前数组的第一个对象，则设置相对的数组current偏移量
            this.setState(state => ({
                current: state.current + firstIndex
            }), this.setDOMStyle);
        } else if (prevP.list.length !== this.props.list.length) {
            // 否则直接setDomStyle
            this.setDOMStyle();
        }
    }

    resetPinch() {
        this.pinch && this.pinch.destroy();
        this.props.pinch && this.setPinch();
    }

    resetMax() {
        this.max = this.$dom[this.props.direction == 'lr' ? 'clientWidth' : 'clientHeight'];
    }

    // 是否需要复用dom
    get isUseOpt() {
        return this.props.cacheDOMLen > 0 && this.props.list.length > this.props.cacheDOMLen;
    }

    /**
     * 矫正change
     *
     * @param {any} v
     * @returns
     * @memberof Swiper
     */
    fixChange(v) {
        if (v > 0) {
            return Math.min(v, this.max);
        }
        return Math.max(v, -this.max);
    }

    // 获取位移
    getChange(change) {
        // 对change的值进行过滤
        // 开启最大最小距离校验
        let C = this.props.direction == 'lr' ? change.x : change.y;


        if (this.isCanMove()) {
            return Math.max(-this.max, Math.min(this.max, C));
        }

        const { current } = this.state;
        const { list } = this.props;

        if (current == 0) {
            // 最大值0 最小值不限制
            C = Math.max(-this.max, Math.min(0, C));
        }

        if (current == list.length - 1) {
            // 最大值不限制 最小值为0
            C = Math.max(0, Math.min(this.max, C));
        }

        return C;
    }

    // 位移
    transform(v) {
        const { direction } = this.props;
        const translate = [
            direction === 'lr' ? v : 0,
            direction === 'ud' ? v : 0,
            0,
        ].join(',');
        return `;
            -webkit-transform: translate3d(${translate});
            transform: translate3d(${translate});
        `;
    }

    // 设置初始位置
    setStyle(v) {
        const { direction } = this.props;
        const LR = direction == 'lr';
        return `;
            ${LR ? 'left' : 'top'}: ${v};
            ${LR ? 'top' : 'left'}: 0;
            height: 100%;
            width: 100%;
        `;
    }

    // 是否响应滑动
    isCanMove() {
        const { list, loop } = this.props;
        if (loop) {
            return true;
        }

        const { current } = this.state;

        // 不可循环轮播
        return !(current == 0 || current == list.length - 1); // 第一个元素，向右滑动
    }

    /**
     * 获取被缩放对象
     *
     * @param {any} ele
     * @returns
     * @memberof Swiper
     */
    getScaleTarget(ele) {
        return ele.querySelector('.scale-target') || ele;
    }

    // get change from own event listener
    listenChange() {
        // 不响应用户触摸行为
        const { touch = {}, preventDefault } = this.props;
        if (!touch.enable) {
            return;
        }
        let initX = 0;
        this.touchEvent = new TouchEvent(this.$dom, {
            finger: 1,
            preventDefault,
        })
            .on('nativeStart', () => {
                this.stopAutoPlay();
                // document.body.style.cssText = ';overflow: hidden;'
            })
            .on('start', () => {
                this.resetMax();
                initX = 0;
            })
            .on('change', (change, state, event) => {
                const { list, direction, onSwiperMoving } = this.props;
                if (state.direction == direction && this.$s) {
                    event.preventDefault();
                    // 处理在切换过程中，手动打断动画的情况
                    // if (this.transitioning) {
                    //     initX = +getComputedStyle(this.$s).transform.match(/\((.+)\)/)[1].split(',')[4]
                    //     toggleTransition(this.$s, false)
                    //     this.timeout.clear()
                    //     this.transitioning = false
                    // }
                    util.RAF(() => {
                        if (!this.$s) {
                            return;
                        }

                        const C = this.getChange(change);
                        // 处理只有两个元素
                        if (list.length == 2) {
                            // this.$next.style.cssText += this.setStyle(`${-C / Math.abs(C) * 100}%`)
                        }

                        this.$s.style.cssText += this.transform(`${this.fixChange(C + initX) / this.max * 100}%`);

                        onSwiperMoving(C, this.state.current);
                    });
                }
            })
            .on('end', (change, done, state) => {
                const { direction } = this.props;

                if (state.direction === direction) {
                    util.RAF(() => {
                        const C = this.getChange(change) + initX;
                        const isEqMax = Math.abs(this.fixChange(C)) === this.max;
                        if (C > touch.distance) {
                            this.go(-1, isEqMax);
                        } else if (C < -touch.distance) {
                            this.go(1, isEqMax);
                        } else if (C != 0) {
                            this.go(0);
                        }
                    });
                }
                done();
            })
            .on('nativeEnd', () => {
                // document.body.style.cssText = ';overflow: unset;'
                this.startAutoPlay();
            });
    }

    // 阻止自动播放
    stopAutoPlay = () => {
        clearInterval(this.autoPlayInterval);
    }

    // 开启自动播放
    startAutoPlay = () => {
        this.stopAutoPlay();
        // 不执行自动播放
        if (!this.props.autoPlay) {
            return;
        }
        this.autoPlayInterval = setInterval(() => {
            this.go(1);
        }, this.props.autoPlayTime);
    }

    // 监听页面隐藏事件
    addPageListener = () => {
        if (!this.props.autoPlay || !this.props.pageHideDisableAutoPlay) {
            return;
        }
        // 页面隐藏后关闭自动播放，否则开启
        this.pageListener = () => {
            if (document.visibilityState === 'visible') {
                this.startAutoPlay();
            } else {
                this.stopAutoPlay();
            }
        };
        document.addEventListener('visibilitychange', this.pageListener);
    }

    removePageListener = () => {
        document.removeEventListener('visibilitychange', this.pageListener);
    }

    // 设置缩放
    setPinch() {
        if ((this.pinch && !this.pinch.destroy) || !this.$dom) {
            return;
        }

        this.pinch = new Pinch(
            this.$dom,
            this.getScaleTarget(this.$current),
        )
            .on('nativeStart', () => {
                this.stopAutoPlay();
            })
            .on('start', () => {
                this.resetMax();
            })
            .on('change', (change, state) => {
                const { list, direction } = this.props;
                const C = state.direction !== direction ? 0 : this.getChange(change);
                if (this.isCanMove(change) && list.length == 2) {
                    // 处理只有两个元素
                    // this.$next.style.cssText += this.setStyle(`${-C / Math.abs(C) * 100}%`)
                }
                this.$s.style.cssText += this.transform(`${C}px`);
            })
            .on('end', (change, state) => {
                const { direction, touch } = this.props;
                if (state.direction === direction && this.isCanMove(change)) {
                    const C = this.getChange(change);
                    if (C > touch.distance) {
                        this.go(-1);
                    } else if (C < -touch.distance) {
                        this.go(1);
                    } else {
                        this.go(0);
                    }
                }
                this.startAutoPlay();
            });
    }

    handlePosition(origin, i = 0) {
        const { space } = this.props;
        if (!space || i === 0) {
            return origin;
        } if (i < 0) {
            return `calc(${origin} - ${space})`;
        }
        return `calc(${origin} + ${space})`;
    }

    // 重置dom style
    setDOMStyle() {
        let { currentItemOffset } = this;
        if (!this.isUseOpt) {
            currentItemOffset = this.state.current;
        }

        // 移除transform属性，避免动态添加元素不渲染
        this.$s.style.cssText += ';transform: none;';
        [...this.$s.children].forEach(($container, index) => {
            if (index == currentItemOffset) {
                const { $current: prevContainer } = this;
                this.$current = $container;
                (prevContainer !== $container) && this.resetPinch();
            }

            // 当前元素始终处在可视区域内
            const newStyle = this.handlePosition(
                `${(index - currentItemOffset) * 100}%`,
                index - currentItemOffset,
            );

            $container.style.cssText += `;
                position: absolute;
                ${this.setStyle(newStyle)};
            `;
        });
    }

    setOffset(current) {
        const { loop, list, cacheDOMLen } = this.props;
        if (!loop) {
            // 初始值
            if (current + cacheDOMLen <= list.length) {
                this.currentItemOffset = 0;
            } else {
                this.currentItemOffset = cacheDOMLen - 1;
            }
            // 发生变化之后的值
            current + 1;
        }
        return this.currentItemOffset;
    }

    // 监听键盘事件
    addKeyEvents() {
        this.$dom.focus();
        this.$dom.addEventListener('keydown', (event) => {
            switch (event.keyCode) {
                case 39:
                    this.go(1);
                    break;
                case 37:
                    this.go(-1);
                    break;
                case 27:
                    this.close && this.close();
                    break;
                default:
            }
        });
    }

    // 跳转
    go(indexChange = 0, isEqMax = false, fromOut = false) {
        // 如果长度为1，不能切换
        if (this.props.list.length < 2) {
            indexChange = 0;
        }
        if (this.transitioning || !this.$s) return;

        // 超出范围了
        const { current } = this.state;
        const newCurrent = this.fixIndex(current + indexChange);

        // 需要在current的上一个或者下一个临时插入进行渲染
        if (!this.getCanRenderArr().includes(newCurrent)) {
            // 插入渲染，在渲染结束的时候
            this.insertIndex = newCurrent;
            this.forceUpdate(() => {
                this.setDOMStyle();
            });
            indexChange = newCurrent > current ? 1 : -1;
        }

        // 如果切换的index大于1，就先切换到终点位置的前一个或者后一个，优化用户体验
        if (Math.abs(indexChange) > 1) {
            let realChange = 0;
            if (indexChange > 0) {
                realChange = -indexChange + 1;
            } else {
                realChange = -indexChange - 1;
            }

            this.$s.style.cssText += this.transform(this.handlePosition(`${realChange * 100}%`, realChange));
        }

        // 执行动画
        toggleTransition(this.$s, true, this.props.transitionTime);
        // 禁止手势触摸，避免一些不必要的问题
        this.touchEvent.disable();
        this.$s.style.cssText += this.transform(this.handlePosition(`${-indexChange * 100}%`, -indexChange));
        this.transitioning = true;

        this.timeout.add(() => {
            this.transitioning = false;
            toggleTransition(this.$s, false, this.props.transitionTime);
            this.touchEvent.enable();
            // 如果current生变化，重新设置样式，通知变更
            if (indexChange != 0) {
                this.insertIndex = undefined;
                this.state.current = newCurrent;
                this.forceUpdate(() => {
                    this.setDOMStyle();
                    !fromOut && this.props.onChange(newCurrent, indexChange);
                });
            }
        }, !isEqMax && this.props.transitionTime);
    }

    /**
     * 矫正index
     *
     * @param {number} [i=0]
     * @returns
     * @memberof Swiper
     */
    fixIndex(i = 0) {
        const { list } = this.props;
        i = i < 0 ? list.length + i : i;
        i = i >= list.length ? 0 : i;
        return i;
    }

    /**
     * 获取可以渲染的index集合
     * @returns
     * @memberof Swiper
     */
    getCanRenderArr() {
        const { list, cacheDOMLen } = this.props;
        const { current } = this.state;

        if (this.isUseOpt) {
            const arr = Array.from({ length: cacheDOMLen })
                .map((i, index) => this.fixIndex(index + current - this.currentItemOffset));
            // 判断是否需要插入临时元素，解决 go index 溢出问题
            // eg: 当前dom [1 2 3]，却要跳转到 4
            return insertItem(arr, current, this.insertIndex);
        }
        return list.map((i, index) => index);
    }

    get className() {
        const { S = {}, list = [] } = this.props;
        const { current } = this.state;

        let disableClass = '';
        if (list.length > 1) {
            disableClass = [
                current != 0 && 'prev', // 阻止向上一个滚动
                current != list.length - 1 && 'next', // 阻止向下一个滚动
            ]
                .filter(i => i)
                .map(i => `wpt-swiper-disable-${this.props.direction}-${i}`)
                .join(' ');
        }

        return [
            S.wrap,
            `wpt-swiper-direction-${this.props.direction}`, // 添加swiper滚动方向class
            disableClass, // 添加swiper阻止事件方向
        ]
            .filter(i => i)
            .join(' ');
    }

    render() {
        const { style, list, S = {} } = this.props;
        const { current } = this.state;

        return (
            <div
                ref={(d) => { this.$dom = d; }}
                style={{ overflow: 'hidden', outline: 'none', ...style }}
                className={this.className}
                // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
                tabIndex="0"
            >
                <div
                    className={S.container}
                    ref={(d) => { this.$s = d; }}
                    style={{ position: 'relative', height: '100%', willChange: 'transform' }}
                >
                    {this.getCanRenderArr().map((i) => {
                        const xx = list[i];
                        const key = typeof xx === 'object' && xx._key ? xx._key : i;

                        return (
                            <div key={key}>
                                {this.props.renderItem(list[i], i, current)}
                            </div>
                        );
                    })}
                </div>
                {this.props.renderChildren({ current, list })}
            </div>
        );
    }
}
